%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     角色查询接口
%%% @end
%%% Created : 08. 7月 2021 16:36
%%%-------------------------------------------------------------------
-module(role_query).
-author("liuwentao").

-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([
    start_link/0
    , pid/2
    , pid/4
    , index/2
    , index/4
    , rec/3
    , rec/5
    , attr/3
    , attr/5
    , conn_pid/2
    , conn_pid/4

    , local_pid/2
    , local_pid/4
    , local_conn_pid/2
    , local_conn_pid/4
    , local_index/2
    , local_index/4
    , local_rec/3
    , local_rec/5
    , local_attr/3
    , local_attr/5

    , sync_index/1
    , clean/1

    , request_name/2
    , reset_zero/2
    , update_act_role_power/1
    , get_act_num/0
]
).
-include("common.hrl").
-include("role.hrl").
-include("conn.hrl").
-record(state, {}).

%% ----------------------------------------------------
%% 外部接口
%% ----------------------------------------------------

%% @doc 启动
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% @doc 跨服查询角色进程ID
pid(by_id, {Id, Platform, ZoneId}) -> pid(by_id, Id, Platform, ZoneId).
pid(by_id, Id, Platform, ZoneId) ->
    role_query:local_pid(by_id, Id, Platform, ZoneId);
pid(by_acc, Acc, Platform, ZoneId) ->
    role_query:local_pid(by_acc, Acc, Platform, ZoneId);
pid(by_name, Name, Platform, ZoneId) ->
    role_query:local_pid(by_name, Name, Platform, ZoneId).

%% @doc 跨服查询角色连接进程ID
conn_pid(by_id, {Id, Platform, ZoneId}) -> conn_pid(by_id, Id, Platform, ZoneId).
conn_pid(by_id, Id, Platform, ZoneId) ->
    role_query:local_conn_pid(by_id, Id, Platform, ZoneId);
conn_pid(by_acc, Acc, Platform, ZoneId) ->
    role_query:local_conn_pid(by_acc, Acc, Platform, ZoneId);
conn_pid(by_name, Name, Platform, ZoneId) ->
    role_query:local_conn_pid(by_name, Name, Platform, ZoneId).

%% @doc 跨节点查询角色索引信息
index(by_id, {Id, Platform, ZoneId}) -> index(by_id, Id, Platform, ZoneId).
index(by_id, Id, Platform, ZoneId) ->
    role_query:local_index(by_id, Id, Platform, ZoneId);
index(by_acc, Acc, Platform, ZoneId) ->
    role_query:local_index(by_acc, Acc, Platform, ZoneId);
index(by_name, Name, Platform, ZoneId) ->
    role_query:local_index(by_name, Name, Platform, ZoneId).


%% @doc 跨节点查询角色的某种record数据
%% <div>注意:此函数有一定的风险，如果被查询的角色进程繁忙的话，可能会调用超时</div>
rec(by_id, {Id, Platform, ZoneId}, RecName) -> rec(by_id, Id, Platform, ZoneId, RecName).
rec(by_id, Id, Platform, ZoneId, RecName) ->
    role_query:local_rec(by_id, Id, Platform, ZoneId, RecName);
rec(by_acc, Acc, Platform, ZoneId, RecName) ->
    role_query:local_rec(by_acc, Acc, Platform, ZoneId, RecName);
rec(by_name, Name, Platform, ZoneId, RecName) ->
    role_query:local_rec(by_name, Name, Platform, ZoneId, RecName).

%% @doc 跨节点查询角色的某些属性
%% <div>注意:此函数有一定的风险，如果被查询的角色进程繁忙的话，可能会调用超时</div>
attr(by_id, {Id, Platform, ZoneId}, Pos) -> attr(by_id, Id, Platform, ZoneId, Pos).
attr(by_id, Id, Platform, ZoneId, Pos) ->
    role_query:local_attr(by_id, Id, Platform, ZoneId, Pos);
attr(by_acc, Acc, Platform, ZoneId, Pos) ->
    role_query:local_attr(by_acc, Acc, Platform, ZoneId, Pos);
attr(by_name, Name, Platform, ZoneId, Pos) ->
    role_query:local_attr(by_name, Name, Platform, ZoneId, Pos).

%% @doc 从本节点查询角色进程ID
local_pid(by_id, {Id, Platform, ZoneId}) -> local_pid(by_id, Id, Platform, ZoneId).
local_pid(by_id, Id, Platform, ZoneId) ->
    case local_index(by_id, Id, Platform, ZoneId) of
        {ok, #id_index{pid = Pid}} -> {ok, Pid};
        {error, Reason} -> {error, Reason}
    end;
local_pid(by_acc, Acc, Platform, ZoneId) ->
    case local_index(by_acc, Acc, Platform, ZoneId) of
        {ok, #acc_index{pid = Pid}} -> {ok, Pid};
        {error, Reason} -> {error, Reason}
    end;
local_pid(by_name, Name, Platform, ZoneId) ->
    case local_index(by_name, Name, Platform, ZoneId) of
        {ok, #name_index{pid = Pid}} -> {ok, Pid};
        {error, Reason} -> {error, Reason}
    end.

%% @doc 从本节点查询角色连接进程ID
local_conn_pid(by_id, {Id, Platform, ZoneId}) -> local_conn_pid(by_id, Id, Platform, ZoneId).
local_conn_pid(by_id, Id, Platform, ZoneId) ->
    case local_index(by_id, Id, Platform, ZoneId) of
        {ok, #id_index{conn_pid = Pid}} -> {ok, Pid};
        {error, Reason} -> {error, Reason}
    end;
local_conn_pid(by_acc, Acc, Platform, ZoneId) ->
    case local_index(by_acc, Acc, Platform, ZoneId) of
        {ok, #acc_index{conn_pid = Pid}} -> {ok, Pid};
        {error, Reason} -> {error, Reason}
    end;
local_conn_pid(by_name, Name, Platform, ZoneId) ->
    case local_index(by_name, Name, Platform, ZoneId) of
        {ok, #name_index{conn_pid = Pid}} -> {ok, Pid};
        {error, Reason} -> {error, Reason}
    end.


%% @doc 从本地点查询角色索引信息
%% <div>注意: 返回{error, not_found}才能确定角色不在线，其它的返回值不一定是角色不在线，可能是其它情况的错误</div>
local_index(by_id, {Id, Platform, ZoneId}) -> local_index(by_id, Id, Platform, ZoneId).
local_index(by_id, Id, Platform, ZoneId) ->
    case catch ets:lookup(role_id_index, {Id, Platform, ZoneId}) of
        [Idx] -> {ok, Idx};
        [] -> {error, not_found};
        Err -> {error, Err}
    end;
local_index(by_acc, Acc, Platform, ZoneId) ->
    case catch ets:lookup(role_acc_index, {Acc, Platform, ZoneId}) of
        [Idx] -> {ok, Idx};
        [] -> {error, not_found};
        Err -> {error, Err}
    end;
local_index(by_name, Name, _Platform, _ZoneId) ->
    case catch ets:lookup(role_name_index, Name) of
        [Idx] -> {ok, Idx};
        [] -> {error, not_found};
        Err -> {error, Err}
    end.


%% @doc 在本节点查询角色的某种record数据
%% <div>注意:此函数有一定的风险，如果被查询的角色进程繁忙的话，可能会调用超时</div>
local_rec(by_id, {Id, Platform, ZoneId}, RecName) -> local_rec(by_id, Id, Platform, ZoneId, RecName).
local_rec(by_id, Id, Platform, ZoneId, RecName) ->
    case local_pid(by_id, Id, Platform, ZoneId) of
        {error, Reason} -> {error, Reason};
        {ok, Pid} -> role:convert(Pid, RecName)
    end;
local_rec(by_acc, Acc, Platform, ZoneId, RecName) ->
    case local_pid(by_acc, Acc, Platform, ZoneId) of
        {error, Reason} -> {error, Reason};
        {ok, Pid} -> role:convert(Pid, RecName)
    end;
local_rec(by_name, Name, Platform, ZoneId, RecName) ->
    case local_pid(by_name, Name, Platform, ZoneId) of
        {error, Reason} -> {error, Reason};
        {ok, Pid} -> role:convert(Pid, RecName)
    end.

%% @doc 在本节点查询角色的某些属性
%% <div>注意:此函数有一定的风险，如果被查询的角色进程繁忙的话，可能会调用超时</div>
local_attr(by_id, {Id, Platform, ZoneId}, Pos) -> local_attr(by_id, Id, Platform, ZoneId, Pos).
local_attr(by_id, Id, Platform, ZoneId, Pos) ->
    case local_pid(by_id, Id, Platform, ZoneId) of
        {error, Reason} -> {error, Reason};
        {ok, Pid} -> role:element(Pid, Pos)
    end;
local_attr(by_acc, Acc, Platform, ZoneId, Pos) ->
    case local_pid(by_acc, Acc, Platform, ZoneId) of
        {error, Reason} -> {error, Reason};
        {ok, Pid} -> role:element(Pid, Pos)
    end;
local_attr(by_name, Name, Platform, ZoneId, Pos) ->
    case local_pid(by_name, Name, Platform, ZoneId) of
        {error, Reason} -> {error, Reason};
        {ok, Pid} -> role:element(Pid, Pos)
    end.


%% @doc 更新索引信息
-spec sync_index(#role{}) -> ok.
sync_index(#role{id = {Rid, Platform, ZoneId}, pid = Pid, name = Name, account = Acc, link = #link{conn_pid = ConnPid}}) ->
    ets:insert(role_name_index, #name_index{id = Rid, acc = Acc, pid = Pid, conn_pid = ConnPid, name = Name}),
    ets:insert(role_id_index, #id_index{id = {Rid, Platform, ZoneId}, acc = Acc, name = Name, pid = Pid, conn_pid = ConnPid}),
    ets:insert(role_acc_index, #acc_index{acc = {Acc, Platform, ZoneId}, id = Rid, name = Name, pid = Pid, conn_pid = ConnPid}),
    ok.

%% @doc 清除某个角色相关的所有缓存信息
-spec clean(#role{}) -> ok.
clean(#role{id = Id = {_, Platform, ZoneId}, account = Acc, name = Name}) ->
    ets:delete(role_acc_index, {Acc, Platform, ZoneId}),
    ets:delete(role_name_index, Name),
    ets:delete(role_id_index, Id),
    ok.


%% @doc 判断角色是否重名,并且占有一个位置
request_name(Name, Info) ->
    ?CALL(?MODULE, {request_name, Name, Info}).

%% @doc 获取当前活跃人数
get_act_num() ->
    case env:get(act_role_power) of
        {_Time, RoleNum, _AllRolePower} ->
            RoleNum;
        _ ->
            0
    end.

%% @doc 0点重刷
reset_zero(#role{is_robot = true}, _) -> ok;
reset_zero(#role{}, _) ->
    update_act_role_power(1),
    ok.

%% @doc 更新活跃玩家的数量及总战力(一个角色一天只能调用一次!)
update_act_role_power(AllPower) ->
    ?MODULE ! {update_act_role_power, AllPower}.


%% ----------------------------------------------------
%% 内部处理
%% ----------------------------------------------------

init([]) ->
    ?INFO("[~w] 正在启动...", [?MODULE]),
    process_flag(trap_exit, true),
    %% 角色缓存信息
    ets:new(role_name_index, [set, named_table, public, {keypos, #name_index.name}]),
    ets:new(role_id_index, [set, named_table, public, {keypos, #id_index.id}]),
    ets:new(role_acc_index, [set, named_table, public, {keypos, #acc_index.acc}]),
    State = #state{},
    ?INFO("[~w] 启动完成", [?MODULE]),
    {ok, State}.

handle_call({request_name, Name, {Acc, ConnPid}}, _From, State) ->
    Reply =
        case sql_conn_mgr:get_one("select count(*) from role where name = ?", [Name]) of
            {error, _Err} ->
                {false, ?T("系统繁忙, 请稍后尝试")};
            {ok, Num} when Num >= 1 ->
                {false, ?T("角色名已有")};
            {ok, _} ->
                case catch ets:match_object(role_name_index, #name_index{name = Name, _ = '_'}) of
                    [] -> %% 取不到角色名
                        ets:insert(role_name_index, #name_index{acc = Acc, conn_pid = ConnPid, name = Name}),
                        ok;
                    [#name_index{}] ->
                        {false, ?T("角色名已经存在")};
                    _ ->
                        {false, ?T("系统繁忙，请稍后再试!")}
                end
        end,
    {reply, Reply, State};

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

%% 统计全服活跃人数与活跃人数 总战力!
handle_info({update_act_role_power, AllPower}, State) ->
    Now = date:unixtime(),
    NewEnv =
        case env:get(act_role_power) of
            {Time, RoleNum, AllRolePower} ->
                case date:unixtime_is_same_day(Now, Time) of
                    true -> %% 同一天,继续累加!
                        {Now, RoleNum + 1, AllRolePower + AllPower};
                    false ->%% 不同天,记录上一天的数据并且重置
                        env:save(last_act_role_power, {Now, Time, RoleNum, AllRolePower}),
                        {Now, 1, AllPower}
                end;
            _ -> {Now, 1, AllPower}
        end,
    env:save(act_role_power, NewEnv),
    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ?INFO("[~w] 正在关闭...", [?MODULE]),
    ?INFO("[~w] 关闭完成", [?MODULE]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

